package com.zx.security.reactive.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.zx.security.reactive.filter.JwtAuthenticationTokenFilter;
import com.zx.security.reactive.handle.AuthenticationEntryPointImpl;
import com.zx.security.reactive.handle.LogoutSuccessHandlerImpl;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Optional;

/**
 * <p>
 * description: security 配置  <br>
 * create: 2024-08-15 16:47
 * </p>
 *
 * @author zhou  xun
 */
@Slf4j
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@RequiredArgsConstructor
public class DefaultSecurityConfig {
    /**
     * jackson对象映射器
     */
    private final ObjectMapper objectMapper;
    /**
     * 认证失败处理类
     */
    private final AuthenticationEntryPointImpl authenticationEntryPoint;

    /**
     * 退出处理类
     */
    private final LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    /**
     * 白名单路径列表,列表字段名称要和配置文件一致
     */
    @Getter
    @Setter
    private List<String> whitelistPaths;

    /**
     * anyRequest             匹配所有请求路径<br/>
     * access                 SpringEl表达式结果为true时可以访问<br/>
     * anonymous              匿名可以访问<br/>
     * denyAll                用户不能访问<br/>
     * fullyAuthenticated     用户完全认证可以访问（非remember-me下自动登录）<br/>
     * hasAnyAuthority        如果有参数，参数表示权限，则其中任何一个权限可以访问<br/>
     * hasAnyRole             如果有参数，参数表示角色，则其中任何一个角色可以访问<br/>
     * hasAuthority           如果有参数，参数表示权限，则其权限可以访问<br/>
     * hasIpAddress           如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问<br/>
     * hasRole                如果有参数，参数表示角色，则其角色可以访问<br/>
     * permitAll              用户可以任意访问<br/>
     * rememberMe             允许通过remember-me登录的用户访问<br/>
     * authenticated          用户登录后可访问<br/>
     * <p>
     * 配置Spring Security的WebFlux安全链。
     * 此方法通过配置ServerHttpSecurity对象来定义WebFlux应用的安全策略。
     * 它返回一个SecurityWebFilterChain对象，该对象包含了所有已配置的安全过滤器，
     * 这些过滤器将按照配置的顺序对进入应用的Web请求进行拦截和处理。
     * </p>
     *
     * @param serverHttpSecurity ServerHttpSecurity对象，用于配置WebFlux的安全策略。
     * @return {@link SecurityWebFilterChain} 配置好的SecurityWebFilterChain对象，包含了所有安全过滤器。
     * @throws Exception 如果在配置过程中发生任何异常，将抛出此异常。
     * @author zhou  xun
     * @since 2024-08-15
     */
    @Bean
    public SecurityWebFilterChain securityFilterChain(ServerHttpSecurity serverHttpSecurity) throws Exception {
        log.info("whitelist path:{}", objectMapper.writeValueAsString(whitelistPaths));
        serverHttpSecurity
                // 禁用跨站请求伪造（CSRF）保护，适用于API或前后端分离的场景
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                // 启用CORS（跨源资源共享）支持，使用默认配置
                .cors(Customizer.withDefaults())
                .headers(headerSpec -> headerSpec
                        // 禁用HTTP响应头中的缓存控制，提高安全性
                        .cache(ServerHttpSecurity.HeaderSpec.CacheSpec::disable)
                        // 禁用X-Frame-Options头部，这通常用于防止点击劫持，但在某些情况下可能需要禁用
                        .frameOptions(ServerHttpSecurity.HeaderSpec.FrameOptionsSpec::disable))
                // 配置授权交换规则
                .authorizeExchange(exchange -> {
                    // 动态配置白名单路径
                    // 使用 Optional 和 Stream 处理白名单路径
                    Optional.ofNullable(whitelistPaths)
                            .filter(list -> !list.isEmpty())
                            .ifPresent(list ->
                                    exchange.pathMatchers(list.toArray(new String[0]))
                                            .permitAll()
                            );
                    // 以下路径允许所有用户访问
                    exchange.pathMatchers("/login", "/register", "/captchaImage").permitAll()
                            .pathMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll()
                            .pathMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll();
                    // 其他所有请求都需要身份验证
                    exchange.anyExchange().authenticated();
                })
                // OAuth2资源服务器配置
                .oauth2ResourceServer(oauth2 ->
                        oauth2.jwt(jwt ->
                                // JWT认证转换器，用于将JWT令牌转换为Spring Security的认证对象
                                jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())
                        )
                )
                // 配置异常处理，当发生身份验证异常时，使用自定义的认证入口点
                .exceptionHandling(exceptionHandling -> exceptionHandling.authenticationEntryPoint(authenticationEntryPoint))
                // 配置注销功能
                .logout(logout -> logout.logoutUrl("/logout").logoutHandler(logoutSuccessHandler))
                // 在UsernamePasswordAuthenticationFilter之前添加JWT认证过滤器
                .addFilterBefore(jwtAuthenticationTokenFilter, SecurityWebFiltersOrder.HTTP_BASIC);

        // 构建并返回SecurityWebFilterChain实例
        return serverHttpSecurity.build();
    }


    /**
     * 登录接口中使用
     * 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证，
     * 所以需要在SecurityConfig配置中把AuthenticationManager注入容器 ，如果不在这配置bean 就需要在认证实现中注入
     *
     * @param authenticationConfiguration 身份认真配置
     * @return {@link AuthenticationManager}
     * @throws Exception 异常
     * @author zhou  xun
     * @since 2024-03-01
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }


    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 自定义JWT Converter
     *
     * @return Converter
     */
    @Bean
    public Converter<Jwt, Mono<AbstractAuthenticationToken>> jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    }
}
